#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>
#import <UIKit/UIApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWApplication.h"
#import "framework/FWDBAccess.h"
#import "framework/FWPreferences.h"

#import "PICommon.h"
#import "PIIntegration.h"
#import "PIContextObject.h"
#import "PIAlarm.h"
#import "PIPlugin.h"

#import "PIEventIntegration.h"
#import "PIEvent.h"
#import "PIAttendee.h"


#define toUTF8NSString(str) [NSString stringWithUTF8String:(!str ? "" : str)]

static NSInteger const kSQLBasicEventList = 1;
static NSInteger const kSQLAddChildData = 2;
static NSInteger const kSQLAugmentedEvent = 3;
static NSInteger const kSQLAttendees = 4;
static NSInteger const kSQLAlarms = 5;


@implementation PIEventIntegration


// ******************************************************************************************
#pragma mark -
#pragma mark Fetch a list of basic events

+ (NSArray*)basicEventsList:(FWApplication*)application prefs:(FWPreferences*)prefs fromDate:(NSDate*)startDate toDate:(NSDate*)endDate
{
    // If PI not found then do nothing
    if (!((PIPlugin*)application.fwPlugin).isPocketInformantPresent)
        return [NSArray array];

    NSString* sql = 
    @"select e.id, e.title, e.startdate, e.enddate, e.location, e.isallday, e.busystatus, e.icon, e.recurrenceid, c.title caltitle, c.color, c.icon calicon,"
    " (select count(*) from attendee a where a.eventid=e.id) attendees"
    " from event e"
    " left join calendar c on c.id=e.calendarid"
    " left join itemtagmap m on m.itemid=e.id and m.itemtable='event'"
    " where (e.calendarid=-2 or e.calendarid>=0)";
    
    // Don't return calendars in the ignore list
    if (NSArray* calendars = [prefs arrayValue:kPIOptionsHiddenPICalendars withDefault:nil]) {
        NSMutableString* ignoreCalendars = [NSMutableString stringWithCapacity:calendars.count * 3];
        for (NSString* calendar in calendars) {
            [ignoreCalendars appendString:calendar withSeparator:@","];
        }
        sql = [sql stringByAppendingFormat:@" and e.calendarid not in (%@)/*IgnoreCals*/", ignoreCalendars];
    }
    

    // Only return tags in the show list if we have one
    if (NSArray* tags = [prefs arrayValue:kPIOptionsTagsFilters withDefault:nil]) {
        NSMutableString* selectTags = [NSMutableString stringWithCapacity:tags.count * 3];
        for (NSString* tag in tags) {
            [selectTags appendString:tag withSeparator:@","];
        }
        if (selectTags.length > 0)
            sql = [sql stringByAppendingFormat:@" and m.tagid in (%@)/*SelectTags*/", selectTags];
    }
    
    
    // Ensure the items are in our date range
    sql = [sql stringByAppendingFormat:@" and e.startdate>=%i and e.enddate<=%i",
           (int)[startDate timeIntervalSinceReferenceDate],
           (int)[endDate timeIntervalSinceReferenceDate]];

    
    // Since we are joinging to the tags table we need to get back to a single record per event
    sql = [sql stringByAppendingString:@" group by e.id"];
    
    PIEventIntegration* exec = [[PIEventIntegration alloc] init];
    NSArray* results = FWDBGetRecords(sql, kSQLBasicEventList, nil, application.databasePath, application, exec);
    [exec release];

    // Only return results if we have some
    if (results)
        return results;
    else
        return [NSArray array];
}

- (NSObject*)sqlBasicEventList:(sqlite3_stmt*)sqlStatement
{
    int eventid = sqlite3_column_int(sqlStatement, 0);
    const char *cTitle = (const char*)sqlite3_column_text(sqlStatement, 1);
    double cStart = sqlite3_column_double(sqlStatement, 2);
    double cEnd = sqlite3_column_double(sqlStatement, 3);
    const char *cLocation = (const char*)sqlite3_column_text(sqlStatement, 4);
    int allday = sqlite3_column_int(sqlStatement, 5);
    int busy = sqlite3_column_int(sqlStatement, 6);
    const char *cEventIcon = (const char*)sqlite3_column_text(sqlStatement, 7);
    int recurranceid = sqlite3_column_int(sqlStatement, 8);
    const char *cCalendarName = (const char*)sqlite3_column_text(sqlStatement, 9);
    const char *cCalendarColor = (const char*)sqlite3_column_text(sqlStatement, 10);
    const char *cCalendarIcon = (const char*)sqlite3_column_text(sqlStatement, 11);
    int attendees = sqlite3_column_int(sqlStatement, 12);
    
    UIColor* color;
    NSString *aCalendarColor = toUTF8NSString(cCalendarColor);
    if ([aCalendarColor length] > 0) {
        NSArray* colorElements = [aCalendarColor componentsSeparatedByString:@"~"];
        color = [UIColor colorWithRed:[[colorElements objectAtIndex:0] floatValue]
                                green:[[colorElements objectAtIndex:1] floatValue]
                                 blue:[[colorElements objectAtIndex:2] floatValue]
                                alpha:[[colorElements objectAtIndex:3] floatValue]];
    }
    else
        color = [UIColor clearColor];
    
    NSDate* startDate = allday ? FWLocalDateForUTC([NSDate nullableDateWithTimeIntervalSinceReferenceDate:cStart]) : [NSDate nullableDateWithTimeIntervalSinceReferenceDate:cStart];
    NSDate* endDate = allday ? FWLocalDateForUTC([NSDate nullableDateWithTimeIntervalSinceReferenceDate:cEnd]) : [NSDate nullableDateWithTimeIntervalSinceReferenceDate:cEnd];
    
    PIEvent* event = [PIEvent piEventWithKey:[[NSNumber numberWithInt:eventid] stringValue]
                                       title:toUTF8NSString(cTitle)
                                        icon:toUTF8NSString(cEventIcon)
                                    location:toUTF8NSString(cLocation)
                                   startDate:startDate
                                     endDate:endDate
                                      allDay:allday
                                    calendar:toUTF8NSString(cCalendarName)
                                calendarIcon:toUTF8NSString(cCalendarIcon)
                               calendarColor:color];
    event.hasAttendees = attendees > 0;
    event.repeating = recurranceid != 0;

    switch (busy) {
        case 0:
            event.availability = kPICalAvailabilityBusy;
            break;
        case 1:
            event.availability = kPICalAvailabilityFree;
            break;
        case 2:
            event.availability = kPICalAvailabilityTentative;
            break;
        case 3:
            event.availability = kPICalAvailabilityOutOfOffice;
            break;
        default:
            event.availability = kPICalAvailabilityNotSupported;
            break;
    }

    return event;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Add alarm data to PI events

+ (void)addAlarmDataToEventList:(FWApplication*)app events:(NSArray*)events prefs:(FWPreferences*)prefs
{
    // If PI not found then do nothing
    if (!((PIPlugin*)app.fwPlugin).isPocketInformantPresent)
        return;
    
    
    // Get a list of keys
    NSMutableString* keys = [NSMutableString stringWithCapacity:50];
    NSMutableArray* lookups = [NSMutableArray arrayWithCapacity:events.count];
    for (PIEvent* event in events) {
        if (!event.isNative) {
            [keys appendString:event.key withSeparator:@","];
            
            // Use short list of items for lookup later
            [lookups addObject:event];
        }
    }

    
    if (keys.length > 0) {
        NSString* alarmsSql = [NSString stringWithFormat:
                               @"select m.itemid, a.relativetrigger, a.absolutetrigger, e.startdate, e.isallday"
                               " from alarm a"
                               " join itemalarmmap m on m.alarmid=a.id and m.itemtable='event'"
                               " join event e on e.id=m.itemid"
                               " where m.itemid in (%@)",
                               keys];
        
        // Get a list of alarms for the events in ascending date order
        PIEventIntegration* exec = [[PIEventIntegration alloc] init];
        NSArray* alarms = FWDBGetSortedRecords(alarmsSql, kSQLAlarms, nil, app.databasePath, app, exec, compareAlarmTimes);
        [exec release];

        NSDate* now = [NSDate date];
        for (PIAlarm* alarm in alarms) {
            if (PIEvent* event = [lookups piObjectForKey:alarm.key]) {
                if (!event.alarms) {
                    event.alarms = [NSMutableArray arrayWithObject:alarm];
                    event.firstAlarm = alarm.date;
                    
                    // This is the next alarm if it hasn't yet expired
                    if ([alarm.date isEqualOrLaterThanDate:now])
                        event.nextAlarm = alarm.date;
                }
                else {
                    [(NSMutableArray*)event.alarms addObject:alarm];

                    // This is the next alarm if it hasn't yet expired and we don't have an earlier alarm
                    if ((!event.nextAlarm || event.nextAlarm.isNull) && [alarm.date isEqualOrLaterThanDate:now])
                        event.nextAlarm = alarm.date;
                }
            }
        }
    }
}


//// ******************************************************************************************
//#pragma mark -
//#pragma mark Accessing information from Pocket Informant
//
//+ (NSDictionary*)dataForPIEvent:(NSString*)key application:(FWApplication*)application
//{
//    NSString* sql = [NSString stringWithFormat:
//                     @"select e.id, e.notes, group_concat(g.title, ', ') from event e"
//                     " left join itemtagmap m on m.itemid = e.id"
//                     " left join tag g on m.tagid = g.id"
//                     " where e.id=%@", key];
//    
//    PIEventIntegration* exec = [[PIEventIntegration alloc] init];
//    NSArray* items = FWDBGetRecords(sql, kSQLAugmentedEvent, nil, application.databasePath, application, exec);
//    [exec release];
//
//    return [items objectAtIndex:0];
//}

//- (NSObject*)objectForSQLPIEventRecord:(sqlite3_stmt*)sqlStatement withId:(NSInteger)sqlid context:(NSObject*)context application:(FWApplication*)application 
//{
//    const int eventid = sqlite3_column_int(sqlStatement, 0);
//    const char *cNotes = (const char*)sqlite3_column_text(sqlStatement, 1);
//    const char *cTags = (const char*)sqlite3_column_text(sqlStatement, 2);
//    
//    NSString* notes = toUTF8NSString(cNotes);
//    NSString* tags = toUTF8NSString(cTags);
//    
////    NSMutableDictionary* values = [NSMutableDictionary dictionaryWithObjectsAndKeys:
////                                   [[NSNumber numberWithInt:eventid] stringValue], kPICalListAccessFieldKey,
////                                   nil];
////    
////    if ([notes length] > 0)
////        [values setObject:notes forKey:kPICalFieldNotes];
////    
////    if ([tags length] > 0)
////        [values setObject:tags forKey:kPICalFieldTags];
////    
////    return values;
//    return nil;
//}

+ (PIEvent*)augmentedBasicEvent:(PIEvent*)event application:(FWApplication*)app
{
    PIEvent* augment = [[event copy] autorelease];
    augment.augmented = YES;
    
    PIEventIntegration* exec = [[PIEventIntegration alloc] init];
    FWDatabase* database = NULL;
    
    @try {
        database = FWDBConnectDatabase(app.databasePath, app);
        if (database == NULL)
            return augment;
        
        NSString* augmentSql = [NSString stringWithFormat:
                                @"select e.meetingstatus, e.sensitivity, e.busystatus, group_concat(m.tagid, ',')"
                                " from event e"
                                " left join itemtagmap m on m.itemid = e.id and m.itemtable='event'"
                                " where e.id=%@"
                                " group by e.id",
                                event.key];
        
        NSArray* info = FWDBGetRecordsFromConnection(augmentSql, kSQLAugmentedEvent, nil, database, app, exec);
        info = [info objectAtIndex:0];
        
        NSArray* tags = [info objectAtIndex:3];
        if (tags.count > 0) {
            NSMutableArray* newTags = [NSMutableArray arrayWithCapacity:tags.count];
            NSArray* allTags = [PIIntegration arrayOfTags:app keys:nil database:database];
            for (NSString* key in tags) {
                if (PIContextObject* tag = [allTags piContextObjectForKey:key.intValue])
                    [newTags addObject:tag];
            }
            if (newTags.count > 0) {
                [newTags sortUsingFunction:compareContextObjectNames context:nil];   
                augment.tags = newTags;
            }
        }
        
        //augment.availability = (kPICalAvailabilityType)[[info objectAtIndex:2] intValue];
        //augment.tags = [info objectAtIndex:3];
        
        NSString* attendeesSql = [NSString stringWithFormat:@"select commonname, email, status"
                                  " from attendee"
                                  " where eventid=%@"
                                  " order by commonname",
                                  event.key];
        
        augment.attendees = FWDBGetSortedRecordsFromConnection(attendeesSql, kSQLAttendees, nil, database, app, exec, compareAttendees);
        

//        NSString* alarmsSql = [NSString stringWithFormat:
//                               @"select a.id, a.relativetrigger, a.absolutetrigger"
//                               " from alarm a"
//                               " join itemalarmmap m on m.alarmid = a.id"
//                               " where m.itemid=%@",
//                               event.key];
//        
//        augment.alarms = FWDBGetRecordsFromConnection(alarmsSql, kSQLAlarms, event.startDate, database, app, exec);
        
        return augment;
    }
    @finally {
        [exec release];
        FWDBDisconnectDatabase(database);
    }
}

- (NSObject*)sqlAugmentedEvent:(sqlite3_stmt*)sqlStatement
{
    const int cStatus = sqlite3_column_int(sqlStatement, 0);
    const int cSensitivity = sqlite3_column_int(sqlStatement, 1);
    const int cBusy = sqlite3_column_int(sqlStatement, 2);
    const char *cTags = (const char*)sqlite3_column_text(sqlStatement, 3);

    NSString* tags = toUTF8NSString(cTags);

    return [NSArray arrayWithObjects:
            [NSNumber numberWithInt:cStatus],
            [NSNumber numberWithInt:cSensitivity],
            [NSNumber numberWithInt:cBusy],
            [tags componentsSeparatedByString:@","],
            nil];
}

- (NSObject*)sqlAttendees:(sqlite3_stmt*)sqlStatement
{
    const char *cCommonName = (const char*)sqlite3_column_text(sqlStatement, 0);
    const char *cEmail = (const char*)sqlite3_column_text(sqlStatement, 1);
    const int cStatus = sqlite3_column_int(sqlStatement, 2);

    NSString* name = toUTF8NSString(cCommonName);
    NSString* email = toUTF8NSString(cEmail);
    
    if ((name.length == 0) && (email.length > 0))
        name = email;
    
    return [PIAttendee attendeeWithName:name status:kPIParticipantStatusUnknown];
}

- (NSObject*)sqlAlarms:(sqlite3_stmt*)sqlStatement context:(NSObject*)context
{
    const int cId = sqlite3_column_int(sqlStatement, 0);
    const int relative = sqlite3_column_int(sqlStatement, 1);
    const int absolute = sqlite3_column_int(sqlStatement, 2);
    
    if (!context) {
        double cStart = sqlite3_column_double(sqlStatement, 3);
        const int allDay = sqlite3_column_int(sqlStatement, 4);
        if (allDay)
            context = FWLocalDateForUTC([NSDate nullableDateWithTimeIntervalSinceReferenceDate:cStart]);
        else
            context = [NSDate nullableDateWithTimeIntervalSinceReferenceDate:cStart];
    }
    
    if (relative != 0)
        return [PIAlarm alarmWithKey:[[NSNumber numberWithInt:cId] stringValue]
                        relativeDate:-relative
                            baseDate:(NSDate*)context
                                type:kPICalAlarmTypeNative];
    else
        return [PIAlarm alarmWithKey:[[NSNumber numberWithInt:cId] stringValue]
                                date:[NSDate nullableDateWithTimeIntervalSinceReferenceDate:absolute]
                                type:kPICalAlarmTypeNative];
}


- (NSObject*)objectForSQLRecord:(sqlite3_stmt *)sqlStatement withId:(NSInteger)sqlid context:(NSObject*)context application:(FWApplication *)application
{
    if (sqlid == kSQLBasicEventList)
        return [self sqlBasicEventList:sqlStatement];
    else if (sqlid == kSQLAugmentedEvent)
        return [self sqlAugmentedEvent:sqlStatement];
    else if (sqlid == kSQLAttendees)
        return [self sqlAttendees:sqlStatement];
    else if (sqlid == kSQLAlarms)
        return [self sqlAlarms:sqlStatement context:context];
    else
        return nil;
}

@end
